﻿#include "render/d3d9_render.h"

#include <rtc_base/task_utils/to_queued_task.h>
#include <rtc_base/logging.h>
#include <libyuv.h>

#include <d3d9.h>

#include "base/xrtc_global.h"

namespace xrtc {

D3D9Render::D3D9Render(void* hwnd, 
    rtc::VideoSourceInterface<webrtc::VideoFrame>* track_to_render) :
    hwnd_(hwnd),
    track_to_render_(track_to_render)
{
    render_rect_.left = 0;
    render_rect_.top = 0;
    render_rect_.right = 0;
    render_rect_.bottom = 0;
}

D3D9Render::~D3D9Render() {
    XRTCGlobal::Instance()->worker_thread()->Invoke<void>(RTC_FROM_HERE,
        [=]() {
            if (d3d9_surface_) {
                d3d9_surface_->Release();
                d3d9_surface_ = nullptr;
            }

            if (d3d9_device_) {
                d3d9_device_->Release();
                d3d9_device_ = nullptr;
            }

            if (d3d9_) {
                d3d9_->Release();
                d3d9_ = nullptr;
            }

            if (rgb_buffer_) {
                delete[] rgb_buffer_;
                rgb_buffer_ = nullptr;
            }

            rgb_buffer_size_ = 0;
            running_ = false;
        });
}

void D3D9Render::Start() {
    XRTCGlobal::Instance()->worker_thread()->PostTask(webrtc::ToQueuedTask([=]() {
        if (running_) {
            RTC_LOG(LS_WARNING) << "d3d9 render already start, ignore";
            return;
        }

        running_ = true;

        track_to_render_->AddOrUpdateSink(this, rtc::VideoSinkWants());
    }));
}

void D3D9Render::Stop() {
    XRTCGlobal::Instance()->worker_thread()->PostTask(webrtc::ToQueuedTask([=]() {
        if (!running_) {
            return;
        }

        running_ = false;
        track_to_render_->RemoveSink(this);
    }));
}

void D3D9Render::Resize() {
    XRTCGlobal::Instance()->worker_thread()->PostTask(webrtc::ToQueuedTask([=]() {
        if (d3d9_device_) {
            d3d9_device_->Release();
            d3d9_device_ = nullptr;
        }
    }));
}

void D3D9Render::OnFrame(const webrtc::VideoFrame& frame) {
    XRTCGlobal::Instance()->worker_thread()->PostTask(webrtc::ToQueuedTask([=]() {
        if (!TryInit(frame)) {
            return;
        }

        DoRender(frame);
    }));
}

bool D3D9Render::TryInit(const webrtc::VideoFrame& frame) {
    do {
        if (!d3d9_ || !d3d9_device_ || !d3d9_surface_) {
            break;
        }

        if (width_ != frame.width() || height_ != frame.height()) {
            break;
        }

        return true;

    } while (false);

    // 检查窗口句柄是否有效
    if (!IsWindow((HWND)hwnd_)) {
        RTC_LOG(LS_WARNING) << "invalid hwnd";
        return false;
    }

    // 获取显示窗口的大小
    RECT rt_viewport;
    if (!GetClientRect((HWND)hwnd_, &rt_viewport)) {
        RTC_LOG(LS_WARNING) << "get client rect failed";
        return false;
    }

    float w1 = rt_viewport.right - rt_viewport.left;
    float h1 = rt_viewport.bottom - rt_viewport.top;

    // 视频的大小
    float w2 = (float)frame.width();
    float h2 = (float)frame.height();

    // 计算目标区域位置
    int dst_w = 0;
    int dst_h = 0;
    int x = 0;
    int y = 0;

    if (w1 * h2 > w2 * h1) { // 需要在左右两侧填充黑边
        dst_w = w2 * h1 / h2; // 可以保证显示的宽高比和图像宽高比一致
        dst_h = h1;
        x = (w1 - dst_w) / 2;
        y = 0;
    }
    else { // 需要在上下填充黑边
        dst_w = w1;
        dst_h = h2 * w1 / w2; // 可以保证显示的宽高比和图像宽高比一致
        x = 0;
        y = (h1 - dst_h) / 2;
    }

    render_rect_.left = x;
    render_rect_.top = y;
    render_rect_.right = x + dst_w;
    render_rect_.bottom = y + dst_h;

    // 1. 创建D3D9对象
    if (!d3d9_) {
        d3d9_ = Direct3DCreate9(D3D_SDK_VERSION);
        if (!d3d9_) {
            RTC_LOG(LS_WARNING) << "create d3d9 failed";
            return false;
        }
    }

    // 2. 创建D3D9 device
    if (!d3d9_device_) {
        D3DPRESENT_PARAMETERS d3dpp;
        ZeroMemory(&d3dpp, sizeof(d3dpp));
        d3dpp.Windowed = true; // 窗口渲染模式
        // 一旦后台缓冲表面的数据被复制到前台，这个缓冲表面的数据就没用了
        d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
        d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

        HRESULT res = d3d9_->CreateDevice(
            D3DADAPTER_DEFAULT, // 指定要表示的物理设备，默认是显示器
            D3DDEVTYPE_HAL,     // 设备类型，支持硬件加速
            (HWND)hwnd_,        // 渲染窗口句柄
            // 顶点混合处理，多线程处理
            D3DCREATE_MIXED_VERTEXPROCESSING | D3DCREATE_MULTITHREADED,
            &d3dpp,
            &d3d9_device_);
        if (FAILED(res)) {
            RTC_LOG(LS_WARNING) << "create d3d9 device failed";
            return false;
        }
    }

    // 3. 创建一个离屏表面
    if (d3d9_surface_) {
        d3d9_surface_->Release();
        d3d9_surface_ = nullptr;
    }

    HRESULT res = d3d9_device_->CreateOffscreenPlainSurface(
        frame.width(),
        frame.height(),
        D3DFMT_X8R8G8B8, // XRGB
        D3DPOOL_DEFAULT, // 默认的资源池
        &d3d9_surface_,
        NULL);
    if (FAILED(res)) {
        RTC_LOG(LS_WARNING) << "create d3d9 surface failed";
        return false;
    }

    width_ = frame.width();
    height_ = frame.height();

    return true;
}

void D3D9Render::DoRender(const webrtc::VideoFrame& frame) {
    // 1. 创建RGB buffer
    size_t size = frame.width() * frame.height() * 4;
    if (size != rgb_buffer_size_) {
        if (rgb_buffer_) { // 图像分辨率发生变化,需要重新分配内存空间
            delete[] rgb_buffer_;
        }

        rgb_buffer_ = new char[size];
        rgb_buffer_size_ = size;
    }

    // 2. 将YUV的图像格式转换成RGB的格式
    rtc::scoped_refptr<webrtc::I420BufferInterface> buffer(
        frame.video_frame_buffer()->ToI420());
    libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(),
        buffer->DataU(), buffer->StrideU(),
        buffer->DataV(), buffer->StrideV(),
        (uint8_t*)rgb_buffer_, frame.width() * 4,
        frame.width(), frame.height());

    // 3. 将RGB数据拷贝到离屏表面
    // 3.1 锁定拷贝区域
    HRESULT res;
    D3DLOCKED_RECT d3d_rect;
    res = d3d9_surface_->LockRect(&d3d_rect, // 描述锁定的矩形区域的指针
        NULL, // 需要锁定的区域，如果NULL，锁定整个区域
        D3DLOCK_DONOTWAIT // 如果锁定失败，不阻塞程序
        );
    if (FAILED(res)) {
        return;
    }

    // 3.2 拷贝RGB数据
    // 获得表面锁定区域的地址
    byte* pdest = (byte*)d3d_rect.pBits;
    // 表面锁定区域每一行的大小
    int stride = d3d_rect.Pitch;
    int rgb_stride = frame.width() * 4;

    if (stride == rgb_stride) {
        memcpy(pdest, rgb_buffer_, rgb_buffer_size_);
    }
    else if (stride > rgb_stride) {
        char* src = rgb_buffer_;
        for (int i = 0; i < frame.height(); ++i) {
            memcpy(pdest, src, rgb_stride);
            pdest += stride;
            src += rgb_stride;
        }
    }

    // 3.3 解除锁定
    d3d9_surface_->UnlockRect();

    // 4. 清除后台缓冲表面
    d3d9_device_->Clear(0, // 第2个参数：矩形数组的长度
        NULL, // 需要清除的矩形数组，如果NULL，清除所有
        D3DCLEAR_TARGET, // 清除渲染目标
        D3DCOLOR_XRGB(20, 20, 20), // 指定清除的颜色
        1.0f, 0);

    // 5. 拷贝离屏表面数据到后台缓冲表面
    d3d9_device_->BeginScene();
    // 获取后台缓冲表面
    IDirect3DSurface9* pback_buffer = nullptr;
    d3d9_device_->GetBackBuffer(0, // 交换链索引
        0, // 后台缓冲表面的索引
        D3DBACKBUFFER_TYPE_MONO, // 后台缓冲的类型
        &pback_buffer);

    d3d9_device_->StretchRect(d3d9_surface_, // 源表面的地址
        NULL, // 源表面需要拷贝的区域，如果是NULL，拷贝整个表面
        pback_buffer, // 目标表面的地址
        &render_rect_, // 需要拷贝到目标表面的区域，如果是NULL，需要拷贝到整个目标表面
        D3DTEXF_LINEAR // 线性插值缩放算法
    );

    d3d9_device_->EndScene();

    // 6. 表面翻转，显示画面
    d3d9_device_->Present(NULL, NULL, NULL, NULL);

    // 7. 清除后台缓冲
    pback_buffer->Release();
}

} // namespace xrtc